/*NOTICE

This software was produced for the U. S. Government
under Contract No. FA8721-09-C-0002, and is
subject to the Rights in Noncommercial Computer Software
and Noncommercial Computer Software Documentation Clause
(DFARS) 252.227-7014 (JUN 1995)

(c) 2009 The MITRE Corporation. All Rights Reserved.
*/
package org.mitre.xmltransform.spmif;

import org.mitre.xmltransform.XmlInputDocument;
import org.mitre.xmltransform.XmlOutputDocument;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class ComboStep extends HierarchyStep {
	private static final int DEFAULT_REPETITION_FACTOR = 1;

	public ComboStep(String pId, String pExecId, Node pNode, String pNodeName, XmlInputDocument pInput, XmlOutputDocument pOutput) {
		super(pId, pExecId, pNode, pNodeName, pInput, pOutput);
		mNodeName = null;
	}

	public String getIdPrefix() {
		return "COMBO";
	}

	public void addChild(HierarchyStep pChild) {
		throw new IllegalArgumentException("Combo steps can not have children");
	}

	private String buildCombinedFragmentName(String pInteractionOperator) {
		StringBuffer result = new StringBuffer();
		if (pInteractionOperator.equals("par")) {
			result.append("PAR");
		} else if (pInteractionOperator.equals("loop")) {
			result.append("REP");
		} else if (pInteractionOperator.equals("alt") || pInteractionOperator.equals("opt")) {
			result.append("CASE");
		} else {
			result.append("CF");
		}
		String nodeId = mInput.getInputValue(getNode(), "@xmi.id");
		NodeList allNodes = mInput.getInputNodes("//UML2:CombinedFragment");
		Node node;
		String xmiId;
		for (int i = 0; i < allNodes.getLength(); i++) {
			node = allNodes.item(i);
			xmiId = mInput.getInputValue(node, "@xmi.id");
			if (xmiId.equals(nodeId)) {
				result.append(i+1);
			}
		}
		return result.toString();			
	}

	private void generateInteractionOperandExpandedNodes(Element pParentElement, NodeList pOperands) {
		if (pOperands == null) return;
		Node n;
		Element expandedNode;
		String expandedNodeName, executionGraphName;
		for (int i = 0; i < pOperands.getLength(); i++) {
			n = pOperands.item(i);
			expandedNode = mOutput.addElement(pParentElement, "ExpandedNode", null);
			expandedNodeName = mInput.buildNodeName(n, "EN");
			executionGraphName = mInput.buildNodeName(n, null);
			expandedNode.setAttribute("NodeName", expandedNodeName);
			expandedNode.setAttribute("EGname", executionGraphName);
		}
	}
			
	public void generateStep(Element pParentElement, Device pLocation) {
		Node node = this.getNode();
		Element compoundNode = mOutput.addElement(pParentElement, "CompoundNode", null);
		int lifelinesCovered = Integer.parseInt(mInput.getInputValue(node, "count(UML2:InteractionFragment.covered/UML2:Lifeline)"));
		String interactionOperator = mInput.getInputValue(node, "@interactionOperator");
		if (interactionOperator.equals("loop")) {
			mNodeName = buildCombinedFragmentName(interactionOperator);
			Element repetitionNode = mOutput.addElement(compoundNode, "RepetitionNode",  null);
			repetitionNode.setAttribute("NodeName", mNodeName);
			mInput.setVariable("messageId", mInput.getInputValue(node, "UML2:CombinedFragment.operand/UML2:InteractionOperand/UML2:InteractionOperand.fragment/UML2:EventOccurrence[UML2:MessageEnd.receiveMessage][position() = 1]/UML2:MessageEnd.receiveMessage/UML2:Message/@xmi.idref"));
			mInput.setVariable("tagDefinitionId", mInput.getInputValue(node, "ancestor::UML:Model/UML:Namespace.ownedElement/UML:TagDefinition[@name='PArep']/@xmi.id"));
			String repetitionFactor;
			if (mInput.evaluateBooleanInputExpression(node, "(string-length($tagDefinitionId) > 0) and boolean(ancestor::UML2:Interaction/UML2:Interaction.message/UML2:Message[@xmi.id=$messageId]/UML:ModelElement.taggedValue/UML:TaggedValue/UML:TaggedValue.type/UML:TagDefinition[@xmi.idref=$tagDefinitionId])")) {
				repetitionFactor = mInput.getInputValue(node, "ancestor::UML2:Interaction/UML2:Interaction.message/UML2:Message[@xmi.id=$messageId]/UML:ModelElement.taggedValue/UML:TaggedValue[UML:TaggedValue.type/UML:TagDefinition[@xmi.idref=$tagDefinitionId]]/UML:TaggedValue.dataValue");
			} else {
				repetitionFactor = Integer.toString(DEFAULT_REPETITION_FACTOR);
			}
			repetitionNode.setAttribute("RepetitionFactor", repetitionFactor);
			generateInteractionOperandExpandedNodes(repetitionNode, mInput.getInputNodes(node, "UML2:CombinedFragment.operand/UML2:InteractionOperand"));
		} else if (interactionOperator.equals("alt") || interactionOperator.equals("opt")) {
			mNodeName = buildCombinedFragmentName(interactionOperator);
			Element caseNode = mOutput.addElement(compoundNode, "CaseNode", null);
			caseNode.setAttribute("NodeName", mNodeName);
			generateInteractionOperandExpandedNodes(caseNode, mInput.getInputNodes(node, "UML2:CombinedFragment.operand/UML2:InteractionOperand"));
		} else if (interactionOperator.equals("break")) {
			System.out.println("break is an unsupported interaction operator. The break combined fragment will be ignored.");
		} else if (interactionOperator.equals("critical")) {
			System.out.println("critical is an unsupported interaction operator. The critical combined fragment will be ignored.");
		} else if (interactionOperator.equals("assert") || interactionOperator.equals("strict") || (interactionOperator.equals("seq") && lifelinesCovered == 1)) {
			generateInteractionOperandExpandedNodes(compoundNode, mInput.getInputNodes(node, "UML2:CombinedFragment.operand/UML2:InteractionOperand"));
		} else if (interactionOperator.equals("neg")) {
			// doing nothing seems like the right behavior?
		} else if (interactionOperator.equals("par") || (interactionOperator.equals("seq") && lifelinesCovered > 1)) {
			mNodeName = buildCombinedFragmentName(interactionOperator);
			Element pardoNode = mOutput.addElement(compoundNode, "PardoNode", null);
			pardoNode.setAttribute("NodeName", mNodeName);
			generateInteractionOperandExpandedNodes(pardoNode, mInput.getInputNodes(node, "UML2:CombinedFragment.operand/UML2:InteractionOperand"));
		}
	}

	public String generateArcs(Element pParentElement, String pFromNode) {
		if (mNodeName == null) return pFromNode;
		if (pFromNode != null) {
			Element arcNode = mOutput.addElement(pParentElement, "Arc", null);
			arcNode.setAttribute("FromNode", pFromNode);
			arcNode.setAttribute("ToNode", mNodeName);
		}
		return mNodeName;
	}
	
	private String mNodeName;
}
